<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse events for compatibility after a tap</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<style>
#parent, #child {
  width: 300px;
  height: 64px;
  padding: 16px;
}
#parent {
  background-color: black;
}
#child {
  background-color: gray;
}
</style>
<script>
"use strict";

addEventListener("load", t => {
  let events = [];
  for (const type of ["mousemove",
                      "mousedown",
                      "mouseup",
                      "click",
                      "dblclick",
                      "contextmenu",
                      "touchend"]) {
    if (type == "touchend") {
      addEventListener(type, event => {
        events.push({type: type, target: event.target});
      }, {capture: true});
    } else {
      addEventListener(type, event => {
        events.push({
          type: event.type,
          target: event.target,
          detail: event.detail,
          button: event.button,
          buttons: event.buttons,
        });
      }, {capture: true});
    }
  }

  function stringifyEvents(arrayOfEvents) {
    if (!arrayOfEvents.length) {
      return "[]";
    }
    function stringifyEvent(event) {
      return `{ type: ${event.type}, target: ${
        event.target.id || event.target.nodeName
      }${
        event.detail !== undefined ? `, detail: ${event.detail}` : ""
      }${
        event.button !== undefined ? `, button: ${event.button}` : ""
      }${
        event.buttons !== undefined ? `, buttons: ${event.buttons}` : ""
      } }`;
    }
    let ret = "";
    for (const event of arrayOfEvents) {
      if (ret === "") {
        ret = "[ ";
      } else {
        ret += ", ";
      }
      ret += stringifyEvent(event);
    }
    return ret + " ]";
  }
  const child = document.getElementById("child");
  const parent = child.parentNode;

  function promiseInitPointer() {
    return new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(0, 0, {origin: document.body})
      .send();
  }
  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(5, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .send();
    assert_equals(
      stringifyEvents(events),
      stringifyEvents([
        { type: "touchend", target: child },
        { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
        { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
        { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
      ])
    );
  }, "Single tap should cause a click");

  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    child.addEventListener("touchstart", event => {
      event.preventDefault();
    }, {once: true});
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(105, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .send();
    assert_equals(
      stringifyEvents(events),
      stringifyEvents([
        { type: "touchend", target: child },
      ])
    );
  }, "Single tap whose touchstart is consumed should not cause a click");

  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    child.addEventListener("touchend", event => {
      event.preventDefault();
    }, {once: true});
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(105, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .send();
    assert_equals(
      stringifyEvents(events),
      stringifyEvents([
        { type: "touchend", target: child },
      ])
    );
  }, "Single tap whose touchend is consumed should not cause a click");

  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(5, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .pointerDown()
      .pointerUp()
      .send();
    assert_in_array(
      stringifyEvents(events),
      [
        // Currently, WebDriver does not have a strict way to synthesize a
        // double click, therefore, it's fine either single click twice or
        // a set of a double-click.
        stringifyEvents([
          { type: "touchend", target: child },
          { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
          { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
          { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
          { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
          { type: "touchend", target: child },
          { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
          { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
          { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
          { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
        ]),
        stringifyEvents([
          { type: "touchend", target: child },
          { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
          { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
          { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
          { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
          { type: "touchend", target: child },
          { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
          { type: "mousedown", target: child, detail: 2, button: 0, buttons: 1 },
          { type: "mouseup", target: child, detail: 2, button: 0, buttons: 0 },
          { type: "click", target: child, detail: 2, button: 0, buttons: 0 },
          { type: "dblclick", target: child, detail: 2, button: 0, buttons: 0 },
        ]),
      ],
    );
  }, "Double tap should cause single-click twice or a double-click");

  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(105, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .pause(1000)
      .pointerDown()
      .pointerUp()
      .send();
    assert_equals(
      stringifyEvents(events),
      stringifyEvents([
        { type: "touchend", target: child },
        { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
        { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
        { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "touchend", target: child },
        { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
        { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
        { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
      ])
    );
  }, "Tapping twice slowly should not cause a dblclick");

  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .pointerMove(5, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .pointerMove(100, 5, {origin: child})
      .pointerDown()
      .pointerUp()
      .send();
    assert_equals(
      stringifyEvents(events),
      stringifyEvents([
        { type: "touchend", target: child },
        { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
        { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
        { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "touchend", target: child },
        { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
        { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
        { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
        { type: "click", target: child, detail: 1, button: 0, buttons: 0 },
      ])
    );
  }, "Tapping too far points should not cause a dblclick");

  promise_test(async () => {
    await promiseInitPointer();
    events = [];
    await new test_driver.Actions()
      .addPointer("touchPointer", "touch")
      .addPointer("touchPointer2", "touch")
      .pointerMove(5, 5, {origin: child, sourceName: "touchPointer"})
      .pointerMove(25, 25, {origin: child, sourceName: "touchPointer2"})
      .pointerDown({sourceName: "touchPointer"})
      .pointerDown({sourceName: "touchPointer2"})
      .pointerUp({sourceName: "touchPointer"})
      .pointerUp({sourceName: "touchPointer2"})
      .send();
    assert_equals(
      stringifyEvents(events),
      stringifyEvents([
        { type: "touchend", target: child },
        { type: "touchend", target: child },
      ])
    );
  }, "Multi tap should not cause mouse events");
}, {once: true});
</script>
</head>
<body><div id="parent"><div id="child"></div></div></body>
</html>
